package beer.coder.walawala.gui.plugin;

import beer.coder.walawala.common.exception.UIException;
import beer.coder.walawala.common.logger.Log;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.stage.Stage;

/**
 * TODO
 *
 * @author amath0312
 * @version 1.0
 * @date 2020-04-18 23:13
 */
public class PluginManager {

  private static final PluginManager instance = new PluginManager();
  public static final String LOG_TAG = "PLUGIN_MANAGER";

  public static PluginManager getInstance() {
    return instance;
  }

  protected File pluginFolder;
  protected PluginLoader pluginLoader;
  protected Map<String, PluginWrapper> pluginInstances;

  protected PluginManager() {
    pluginFolder = new File(System.getProperty("user.dir"), "plugins");
    if (!pluginFolder.exists()) {
      pluginFolder.mkdirs();
    }
    pluginInstances = new LinkedHashMap<>();
  }

  public ContextMenu createContextMenu(Stage mainWindow) throws UIException {
    Map<String, Object> menuItemNodes = new LinkedHashMap<>();

    loadPlugins(mainWindow);
    for (PluginWrapper plugin : this.pluginInstances.values()) {
      createMenuNode(menuItemNodes, plugin);
    }

    Log.d(LOG_TAG, menuItemNodes.toString());
    List<MenuItem> menuItems = createMenuItems(null, menuItemNodes);
    return new ContextMenu(menuItems.toArray(new MenuItem[0]));
  }

  protected List<MenuItem> createMenuItems(Menu parent, Map<String, Object> menuItemNodes) {
    List<MenuItem> menuItems = new LinkedList<>();

    for (String menuName : menuItemNodes.keySet()) {
      if (PluginWrapper.class.isInstance(menuItemNodes.get(menuName))) {
        PluginWrapper plugin = (PluginWrapper) menuItemNodes.get(menuName);
        MenuItem menuItem = new MenuItem(plugin.getPlugin().getName());
        menuItem.setOnAction((e) -> {
          try {
            plugin.getPlugin().open(menuItem);
          } catch (UIException ex) {
            ex.printStackTrace();
          }
        });
        if (parent == null) {
          menuItems.add(menuItem);
        } else {
          parent.getItems().add(menuItem);
        }
      } else {
        Menu menu = new Menu(menuName);
        if (parent == null) {
          menuItems.add(menu);
        } else {
          parent.getItems().add(menu);
        }
        Map<String, Object> menuNode = (Map<String, Object>) menuItemNodes.get(menuName);
        createMenuItems(menu, menuNode);
      }
    }
    return menuItems;
  }

  protected void createMenuNode(Map<String, Object> menuItems, PluginWrapper plugin) {
    Map<String, Object> curNode = menuItems;
    String[] menuPath = plugin.getPlugin().getMenuPath();
    for (String m : menuPath) {
      if (!curNode.containsKey(m)) {
        Map<String, Object> val = new LinkedHashMap<>();
        curNode.put(m, val);
      }
      curNode = (Map<String, Object>) curNode.get(m);
    }
    curNode.put(plugin.getPlugin().getName(), plugin);
  }

  public void loadPlugins(Stage mainWindow) throws UIException {
    try {
      if (pluginLoader == null) {
        pluginLoader = createClassLoader();
      }

      pluginLoader.loadAllPluginClasses();
    } catch (IOException e) {
      throw new UIException("create plugin loader error", e);
    }

    for (Class cls : pluginLoader.getPluginClassList()) {
      try {
        PluginWrapper plugin = pluginLoader.createPlugin(cls);
        if (plugin != null) {
          plugin.getPlugin().load(mainWindow);
          pluginInstances.put(plugin.getPlugin().getId(), plugin);
          Log.d(LOG_TAG, "load plugin [%s] success", plugin.getPlugin().getId());
        }
      } catch (Exception e) {
        Log.e(LOG_TAG, "load " + cls.getName() + " error", e);
      }
    }
  }

  public void closePlugins() {
    for (PluginWrapper plugin : pluginInstances.values()) {
      if (plugin != null && plugin.getPlugin() != null) {
        try {
          plugin.getPlugin().close();
        } catch (UIException e) {
          e.printStackTrace();
        }
      }
    }
  }

  public void detachPlugins() {
    for (PluginWrapper plugin : pluginInstances.values()) {
      if (plugin != null && plugin.getPlugin() != null) {
        try {
          plugin.getPlugin().detach();
        } catch (UIException e) {
          e.printStackTrace();
        }
      }
    }
  }

  protected PluginLoader createClassLoader() throws MalformedURLException {
    List<URL> jarFileList = new LinkedList<>();
    for (File file : pluginFolder.listFiles(f -> f.getName().endsWith(".jar"))) {
      jarFileList.add(file.toURI().toURL());
    }
    Log.d(LOG_TAG, "%s: %s", "plugin files", jarFileList);
    URL[] jarFiles = jarFileList.toArray(new URL[0]);

    URLClassLoader classLoader = URLClassLoader
        .newInstance(jarFiles, PluginManager.class.getClassLoader());
    PluginLoader pluginLoader = new PluginLoader(classLoader, jarFiles);

    return pluginLoader;
  }


  public static void main(String[] args)
      throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, UIException {
    getInstance().createContextMenu(null);
  }

}
